@using System.Net
@namespace Oqtane.Modules.Admin.Login
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject IUserService UserService
@inject ISettingService SettingService
@inject IServiceProvider ServiceProvider
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer

@if (PageState.User != null)
{
    <ModuleMessage Message="@Localizer["Info.SignedIn"]" Type="MessageType.Info" />
}
else
{
    @if (!twofactor)
    {
        <form @ref="login" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
            <div class="Oqtane-Modules-Admin-Login" @onkeypress="@(e => KeyPressed(e))">
                @if (_allowexternallogin)
                {
                    <button type="button" class="btn btn-primary col-12" @onclick="ExternalLogin">@Localizer["Use"] @PageState.Site.Settings["ExternalLogin:ProviderName"]</button>
                    <hr class="app-rule mt-3 mb-2" />
                }
                @if (_allowsitelogin)
                {
                    <div class="form-group text-center">
                        <Label Class="control-label" For="username" HelpText="Please enter your Username" ResourceKey="Username">Username:</Label>
                        <input id="username" type="text" @ref="username" class="form-control" placeholder="@Localizer["Username.Placeholder"]" @bind="@_username" @bind:event="oninput" required />
                    </div>
                    <div class="form-group text-center mt-2">
                        <Label Class="control-label" For="password" HelpText="Please enter your Password" ResourceKey="Password">Password:</Label>
                        <div class="input-group">
                            <input id="password" type="@_passwordtype" name="Password" class="form-control" placeholder="@Localizer["Password.Placeholder"]" @bind="@_password" @bind:event="oninput" required />
                            <button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
                        </div>
                    </div>
                    @if (!_alwaysremember)
                    {
                        <div class="form-group text-center mt-2">
                            <div>
                                <input id="remember" type="checkbox" class="form-check-input" @bind="@_remember" />
                                <Label Class="control-label" For="remember" HelpText="Specify if you would like to be signed back in automatically the next time you visit this site" ResourceKey="Remember">Remember Me?</Label>
                            </div>
                        </div>
                    }

                    <div class="btn-group mt-2 col-12" role="group">
                        <button type="button" class="btn btn-primary col-6" @onclick="Login">@SharedLocalizer["Login"]</button>
                        <button type="button" class="btn btn-secondary col-6" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
                    </div>
                    <button type="button" class="btn btn-secondary col-12 mt-4" @onclick="Forgot">@Localizer["ForgotPassword"]</button>

                    @if (_allowpasskeys)
                    {
                        <hr class="app-rule mt-3" />
                        <button type="button" class="btn btn-primary col-12 mt-2" @onclick="Passkey">@Localizer["Passkey"]</button>
                    }

                    @if (PageState.Site.AllowRegistration)
                    { 
                        <hr class="app-rule mt-3" />
                        <div class="text-center mt-2">
                            <NavLink href="@_registerurl">@Localizer["Register"]</NavLink>
                        </div>
                    }
                }
            </div>
        </form>
    }
    else
    {
        <form @ref="login" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
            <div class="container Oqtane-Modules-Admin-Login">
                <div class="form-group">
                    <Label Class="control-label" For="code" HelpText="Please enter the secure verification code which was sent to you by email" ResourceKey="Code">Verification Code:</Label>
                    <input id="code" class="form-control" @bind="@_code" placeholder="@Localizer["Code.Placeholder"]" maxlength="6" required />
                </div>
                <br />
                <button type="button" class="btn btn-primary" @onclick="Login">@SharedLocalizer["Login"]</button>
                <button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Cancel"]</button>
            </div>
        </form>
    }
}

@code {
    private bool _allowsitelogin = true;
    private bool _allowexternallogin = false;
    private bool _allowpasskeys = false;
    private ElementReference login;
    private bool validated = false;
    private bool twofactor = false;
    private string _username = string.Empty;
    private ElementReference username;
    private string _password = string.Empty;
    private string _passwordtype = "password";
    private string _togglepassword = string.Empty;
    private bool _remember = false;
    private bool _alwaysremember = false;
    private string _code = string.Empty;
    private string _registerurl = string.Empty;

    public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
    public override bool? Prerender => true;

    public override List<Resource> Resources => new List<Resource>()
    {
        new Stylesheet(ModulePath() + "Module.css")
    };

    protected override async Task OnInitializedAsync()
    {
        try
        {
            _allowexternallogin = (SettingService.GetSetting(PageState.Site.Settings, "ExternalLogin:ProviderType", "") != "") ? true : false;
            _allowsitelogin = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AllowSiteLogin", "true"));
            _allowpasskeys = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:Passkeys", "false"));
            _alwaysremember = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AlwaysRemember", "false"));

            if (!string.IsNullOrEmpty(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:RegisterUrl", "")))
            {
                _registerurl = SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:RegisterUrl", "");
            }
            else
            {
                _registerurl = NavigateUrl("register");
            }

            _togglepassword = SharedLocalizer["ShowPassword"];

            if (PageState.QueryString.ContainsKey("name"))
            {
                _username = PageState.QueryString["name"];
            }

            if (PageState.QueryString.ContainsKey("token") && !string.IsNullOrEmpty(_username))
            {
                var user = new User();
                user.SiteId = PageState.Site.SiteId;
                user.Username = _username;

                if (PageState.QueryString.ContainsKey("key"))
                {
                    user = await UserService.AddLoginAsync(user, PageState.QueryString["token"], PageState.Site.Settings["ExternalLogin:ProviderType"], PageState.QueryString["key"], PageState.Site.Settings["ExternalLogin:ProviderName"]);
                    if (user != null)
                    {
                        await logger.LogInformation(LogFunction.Security, "External Login Linkage Successful For Username {Username}", _username);
                        AddModuleMessage(Localizer["Success.Account.Linked"], MessageType.Info);						
                    }
                    else
                    {
                        await logger.LogError(LogFunction.Security, "External Login Linkage Failed For Username {Username}", _username);
                        AddModuleMessage(Localizer["Message.Account.NotLinked"], MessageType.Warning);						
                    }
                    _username = "";
                }
                else
                {
                    user = await UserService.VerifyEmailAsync(user, PageState.QueryString["token"]);
                    if (user != null)
                    {
                        await logger.LogInformation(LogFunction.Security, "Email Verified For Username {Username}", _username);
                        AddModuleMessage(Localizer["Success.Account.Verified"], MessageType.Info);						
                    }
                    else
                    {
                        await logger.LogError(LogFunction.Security, "Email Verification Failed For Username {Username}", _username);
                        AddModuleMessage(Localizer["Message.Account.NotVerified"], MessageType.Warning);						
                    }
                }
            }
            else
            {
                if (PageState.QueryString.ContainsKey("status"))
                {
                    AddModuleMessage(Localizer["ExternalLoginStatus." + PageState.QueryString["status"]], MessageType.Info);
                }
            }
        }
        catch (Exception ex)
        {
            await logger.LogError(ex, "Error Loading Login {Error}", ex.Message);
            AddModuleMessage(Localizer["Error.LoadLogin"], MessageType.Error);
        }
    }

    private async Task Login()
    {
        try
        {
            validated = true;
            var interop = new Interop(JSRuntime);
            if (await interop.FormValid(login))
            {
                var hybrid = (PageState.Runtime == Shared.Runtime.Hybrid);
                var user = new User { SiteId = PageState.Site.SiteId, Username = _username, Password = _password, LastIPAddress = SiteState.RemoteIPAddress};

                if (!twofactor)
                { 
                    _remember = _alwaysremember || _remember;
                    user = await UserService.LoginUserAsync(user, hybrid, _remember);
                }
                else
                {
                    user = await UserService.VerifyTwoFactorAsync(user, _code);
                }

                if (user != null && user.IsAuthenticated)
                {
                    await logger.LogInformation(LogFunction.Security, "Login Successful For {Username} From IP Address {IPAddress}", _username, SiteState.RemoteIPAddress);

                    // return url is not specified if user navigated directly to login page
                    var returnurl = (!string.IsNullOrEmpty(PageState.ReturnUrl)) ? PageState.ReturnUrl : PageState.Alias.Path;

                    if (hybrid)
                    {
                        // hybrid apps utilize an interactive login
                        var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider));
                        authstateprovider.NotifyAuthenticationChanged();
                        NavigationManager.NavigateTo(NavigateUrl(returnurl, true));
                    }
                    else
                    {
                        // post back to the Login page so that the cookies are set correctly
                        var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = _username, password = _password, remember = _remember, returnurl = WebUtility.UrlEncode(returnurl) };
                        string url = Utilities.TenantUrl(PageState.Alias, "/pages/login/");
                        await interop.SubmitForm(url, fields);
                    }
                }
                else
                {
                    if (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "required" || (user != null && user.TwoFactorRequired))
                    {
                        twofactor = true;
                        validated = false;
                        AddModuleMessage(Localizer["Message.TwoFactor"], MessageType.Info);
                    }
                    else
                    {
                        if (!twofactor)
                        {
                            await logger.LogInformation(LogFunction.Security, "Login Failed For Username {Username}", _username);
                            AddModuleMessage(Localizer["Error.Login.Fail"], MessageType.Error);
                        }
                        else
                        {
                            await logger.LogInformation(LogFunction.Security, "Two Factor Verification Failed For Username {Username}", _username);
                            AddModuleMessage(Localizer["Error.TwoFactor.Fail"], MessageType.Error);
                        }
                    }
                }
            }
            else
            {
                AddModuleMessage(Localizer["Message.Required.UserInfo"], MessageType.Warning);
            }
        }
        catch (Exception ex)
        {
            await logger.LogError(ex, "Error Performing Login {Error}", ex.Message);
            AddModuleMessage(Localizer["Error.Login"], MessageType.Error);
        }
    }

    private void Cancel()
    {
        NavigationManager.NavigateTo(PageState.ReturnUrl);
    }

    private async Task Forgot()
    {
        try
        {
            if (_username != string.Empty)
            {
                var user = await UserService.GetUserAsync(_username, PageState.Site.SiteId);
                if (user != null)
                {
                    await UserService.ForgotPasswordAsync(user);
                    await logger.LogInformation(LogFunction.Security, "Password Reset Notification Sent For Username {Username}", _username);
                    AddModuleMessage(Localizer["Message.ForgotUser"], MessageType.Info);
                }
                else
                {
                    AddModuleMessage(Localizer["Message.UserDoesNotExist"], MessageType.Warning);
                }
            }
            else
            {
                AddModuleMessage(Localizer["Message.ForgotPassword"], MessageType.Info);
            }

            StateHasChanged();
        }
        catch (Exception ex)
        {
            await logger.LogError(ex, "Error Resetting Password {Error}", ex.Message);
            AddModuleMessage(Localizer["Error.ResetPassword"], MessageType.Error);
        }
    }

    private void Reset()
    {
        twofactor = false;
        _username = "";
        _password = "";
        ClearModuleMessage();
        StateHasChanged();
    }

    private async Task KeyPressed(KeyboardEventArgs e)
    {
        if (e.Code == "Enter" || e.Code == "NumpadEnter")
        {
            await Login();
        }
    }

    private void TogglePassword()
    {
        if (_passwordtype == "password")
        {
            _passwordtype = "text";
            _togglepassword = SharedLocalizer["HidePassword"];
        }
        else
        {
            _passwordtype = "password";
            _togglepassword = SharedLocalizer["ShowPassword"];
        }
    }

    private void ExternalLogin()
    {
        NavigationManager.NavigateTo(Utilities.TenantUrl(PageState.Alias, "/pages/external?returnurl=" + WebUtility.UrlEncode(PageState.ReturnUrl)), true);
    }

    private async Task Passkey()
    {
        // post back to the Passkey page so that the cookies are set correctly
        var interop = new Interop(JSRuntime);
        var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, operation = "request", returnurl = NavigateUrl() };
        string url = Utilities.TenantUrl(PageState.Alias, "/pages/passkey/");
        await interop.SubmitForm(url, fields);
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && PageState.QueryString.ContainsKey("options"))
        {
            // user has initiated a passkey login
            try
            {
                var interop = new Interop(JSRuntime);
                var credential = await interop.RequestCredential(WebUtility.UrlDecode(PageState.QueryString["options"]));
                if (!string.IsNullOrEmpty(credential))
                {
                    // post back to the Passkey page so that the cookies are set correctly
                    var returnurl = (!string.IsNullOrEmpty(PageState.ReturnUrl)) ? PageState.ReturnUrl : PageState.Alias.Path + "/";
                    var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, operation = "login", credential = credential, returnurl = returnurl };
                    string url = Utilities.TenantUrl(PageState.Alias, "/pages/passkey/");
                    await interop.SubmitForm(url, fields);
                }
                else
                {
                    await logger.LogError("Passkey Login Was Not Successful");
                    AddModuleMessage(Localizer["Error.Passkey.Fail"], MessageType.Warning);
                }
            }
            catch (Exception ex)
            {
                await logger.LogError(ex, "Passkey Login Was Not Successful");
                AddModuleMessage(Localizer["Error.Passkey.Fail"], MessageType.Warning);
            }
            return;
        }

        if (firstRender && PageState.User == null && _allowsitelogin)
        {
            if (!string.IsNullOrEmpty(username.Id)) // ensure username is visible in UI
            {
                await username.FocusAsync();
            }
        }

        // redirect logged in user to specified page
        if (PageState.User != null && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
        {
            NavigationManager.NavigateTo(PageState.ReturnUrl);
        }
    }
}
